package io.cici.cc.mybatis.lite.session;

import io.cici.cc.mybatis.lite.binding.MapperRegistry;
import io.cici.cc.mybatis.lite.executor.BatchExecutor;
import io.cici.cc.mybatis.lite.executor.Executor;
import io.cici.cc.mybatis.lite.executor.SimpleExecutor;
import io.cici.cc.mybatis.lite.executor.keygen.KeyGenerator;
import io.cici.cc.mybatis.lite.executor.parameter.ParameterHandler;
import io.cici.cc.mybatis.lite.executor.resultset.ResultContextHandler;
import io.cici.cc.mybatis.lite.executor.resultset.ResultSetHandlerImpl;
import io.cici.cc.mybatis.lite.executor.statement.PreparedStatementHandler;
import io.cici.cc.mybatis.lite.executor.statement.StatementHandler;
import io.cici.cc.mybatis.lite.mapping.*;
import io.cici.cc.mybatis.lite.parse.Node;
import io.cici.cc.mybatis.lite.reflection.MetaDataObject;
import io.cici.cc.mybatis.lite.reflection.ReflectorFactory;
import io.cici.cc.mybatis.lite.reflection.ReflectorFactoryImpl;
import io.cici.cc.mybatis.lite.reflection.factory.ObjectFactory;
import io.cici.cc.mybatis.lite.reflection.factory.ObjectFactoryImpl;
import io.cici.cc.mybatis.lite.reflection.wrapper.DefaultObjectWrapperFactory;
import io.cici.cc.mybatis.lite.reflection.wrapper.ObjectWrapperFactory;
import io.cici.cc.mybatis.lite.sql_source.scripting.LanguageDriver;
import io.cici.cc.mybatis.lite.transaction.Transaction;
import io.cici.cc.mybatis.lite.type.TypeHandlerRegistry;
import io.cici.cc.mybatis.plus.core.MybatisXMLLanguageDriver;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Setter
@Getter
public class Configuration {
    public static final ReflectorFactory reflectorFactory = new ReflectorFactoryImpl();
    public static final ObjectFactory objectFactory = new ObjectFactoryImpl();
    public static final LanguageDriver languageDriver = new MybatisXMLLanguageDriver();
    protected final MapperRegistry mapperRegistry = new MapperRegistry();
    protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    protected final Map<String, MappedStatement> mappedStatements = new ConcurrentHashMap<>();

    protected final Map<String, ResultMappings> resultMaps = new ConcurrentHashMap<>();
    protected final Map<String, ParameterMappings> parameterMaps = new ConcurrentHashMap<>();
    protected final Map<String, KeyGenerator> keyGenerators = new ConcurrentHashMap<>();
    protected final Set<String> loadedResources = new HashSet<>();
    protected final Map<String, Node> sqlIdToNodeMap = new ConcurrentHashMap<>();

    protected Environment environment;

    protected boolean safeResultHandlerEnabled = true;
    protected boolean mapUnderscoreToCamelCase = true;
    protected boolean aggressiveLazyLoading = false;

    protected boolean useGeneratedKeys = false;
    protected boolean useColumnLabel = true;

    protected boolean callSettersOnNulls = false;
    protected boolean useActualParamName = true;
    protected boolean returnInstanceForEmptyRow = false;
    protected boolean shrinkWhitespacesInSql = false;


    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;


    protected Properties variables = new Properties();

    protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();


    public Configuration(Environment environment) {
        this();
        this.environment = environment;
    }

    public Configuration() {


    }


    public void addLoadedResource(String resource) {
        loadedResources.add(resource);
    }

    public boolean isResourceLoaded(String resource) {
        return loadedResources.contains(resource);
    }


    public MetaDataObject newMetaDataObject(Object object) {
        return MetaDataObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
    }

    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, Sql sql) {
        ParameterHandler parameterHandler = Configuration.languageDriver.createParameterHandler(mappedStatement,
                parameterObject, sql);
        return parameterHandler;
    }

    public io.cici.cc.mybatis.lite.executor.resultset.ResultSetHandler newResultSetHandler(Executor executor,
                                                                                           MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultContextHandler resultHandler,
                                                                                           Sql sql) {
        io.cici.cc.mybatis.lite.executor.resultset.ResultSetHandler resultSetHandler = new ResultSetHandlerImpl(
                mappedStatement, resultHandler);
        return resultSetHandler;
    }

    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
                                                Object parameterObject, ResultContextHandler resultContextHandler, Sql sql) {
        StatementHandler statementHandler = new PreparedStatementHandler(executor, mappedStatement, parameterObject,
                resultContextHandler, sql);
        return statementHandler;
    }

    public Executor newExecutor(Transaction transaction) {
        return newExecutor(transaction, defaultExecutorType);
    }

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }

        return executor;
    }

    public void addKeyGenerator(String id, KeyGenerator keyGenerator) {
        keyGenerators.put(id, keyGenerator);
    }

    public Collection<String> getKeyGeneratorNames() {
        return keyGenerators.keySet();
    }

    public Collection<KeyGenerator> getKeyGenerators() {
        return keyGenerators.values();
    }

    public KeyGenerator getKeyGenerator(String id) {
        return keyGenerators.get(id);
    }

    public boolean hasKeyGenerator(String id) {
        return keyGenerators.containsKey(id);
    }

    public void addResultMap(ResultMappings rm) {
        resultMaps.put(rm.getId(), rm);

    }

    public Collection<String> getResultMapNames() {
        return resultMaps.keySet();
    }

    public Collection<ResultMappings> getResultMaps() {
        return resultMaps.values();
    }

    public ResultMappings getResultMap(String id) {
        return resultMaps.get(id);
    }

    public boolean hasResultMap(String id) {
        return resultMaps.containsKey(id);
    }

    public void addParameterMappings(ParameterMappings pm) {
        parameterMaps.put(pm.getId(), pm);
    }

    public Collection<String> getParameterMapNames() {
        return parameterMaps.keySet();
    }

    public Collection<ParameterMappings> getParameterMaps() {
        return parameterMaps.values();
    }

    public ParameterMappings getParameterMap(String id) {
        return parameterMaps.get(id);
    }

    public boolean hasParameterMap(String id) {
        return parameterMaps.containsKey(id);
    }

    public void addMappedStatement(MappedStatement ms) {
        mappedStatements.put(ms.getId(), ms);
    }

    public Collection<String> getMappedStatementNames() {

        return mappedStatements.keySet();
    }

    public Collection<MappedStatement> getMappedStatementIdToMappedStatementMap() {

        return mappedStatements.values();
    }

    public MappedStatement getMappedStatement(String id) {
        return this.getMappedStatement(id, true);
    }

    public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {

        return mappedStatements.get(id);
    }

    public Map<String, Node> getSqlIdToNodeMap() {
        return sqlIdToNodeMap;
    }


    public void addMappers(String packageName) {
        mapperRegistry.addMappers();
    }

    public <T> void addMapper(Class<T> type) {
        mapperRegistry.addMapper(type);
    }

    public <T> T getMapper(Class<T> type, Session session) {
        return mapperRegistry.getMapper(type, session);
    }

    public boolean hasMapper(Class<?> type) {
        return mapperRegistry.hasMapper(type);
    }

    public boolean hasStatement(String statementName) {
        return hasStatement(statementName, true);
    }

    public boolean hasStatement(String statementName, boolean validateIncompleteStatements) {

        return mappedStatements.containsKey(statementName);
    }


}
